import axios from 'axios'
import {
  AxiosCanceler
} from './cancel'
import {
  isFunction,
  get,
  cloneDeep
} from 'lodash'

import {
  AxiosCache
} from './cache'
import { getCacheConfig } from './helper'
import store from '@/store'
export default class AxiosRequest {
  axiosInstance;
  options;
  cache;
  axiosCanceler;

  constructor (options) {
    this.options = options
    this.cache = new AxiosCache()
    this.axiosCanceler = new AxiosCanceler()

    this.axiosInstance = axios.create(options)

    this.setupInterceptors()
  }

  createAxios (config) {
    this.axiosInstance = axios.create(config)
  }

  getAxios () {
    return this.axiosInstance
  }

  configAxios (config) {
    if (!this.axiosInstance) {

    }
    this.createAxios(config)
  }

  setHeader (headers) {
    if (!this.axiosInstance) {
      return
    }
    Object.assign(this.axiosInstance.defaults.headers, headers)
  }

  getTransform () {
    const {
      transform
    } = this.options || {}
    return transform
  }

  setupInterceptors () {
    const transform = this.getTransform()
    if (!transform) {
      return
    }

    const {
      requestInterceptors,
      responseInterceptors,
      requestInterceptorsCatch,
      responseInterceptorsCatch
    } = transform

    this.axiosInstance.interceptors.request.use((config) => {
      const {
        ignoreCancelToken
      } = get(config, 'requestOptions', {})

      const ignoreCancel =
        ignoreCancelToken !== undefined
          ? ignoreCancelToken
          : get(this.options, 'requestOptions.ignoreCancelToken')
      !ignoreCancel && this.axiosCanceler.addPending(config)
      if (requestInterceptors && isFunction(requestInterceptors)) {
        config = requestInterceptors(config, this.options)
      }

      return config
    }, undefined)

    requestInterceptorsCatch &&
      isFunction(requestInterceptorsCatch) &&
      this.axiosInstance.interceptors.request.use(undefined, requestInterceptorsCatch)

    this.axiosInstance.interceptors.response.use((res) => {
      res && this.axiosCanceler.removePending(res.config)
      if (responseInterceptors && isFunction(responseInterceptors)) {
        res = responseInterceptors(res)
      }
      return res
    }, undefined)

    responseInterceptorsCatch &&
      isFunction(responseInterceptorsCatch) &&
      this.axiosInstance.interceptors.response.use(undefined, (error) => {
        return responseInterceptorsCatch(this.axiosInstance, error)
      })
  }

  get (url, config, options) {
    return this.request({
      ...config,
      url,
      method: 'GET'
    }, options)
  }

  post (url, data, config, options) {
    return this.request({
      ...config,
      data,
      url,
      method: 'POST'
    }, options)
  }

  put (url, data, config, options) {
    return this.request({
      ...config,
      data,
      url,
      method: 'PUT'
    }, options)
  }

  delete (url, config, options) {
    return this.request({
      ...config,
      url,
      method: 'DELETE'
    }, options)
  }

  useCache (config) {
    const customRequest = get(config, 'requestOptions.customRequest')
    let cacheConfig = getCacheConfig(get(config, 'requestOptions.cache'))

    if (customRequest && isFunction(customRequest)) {
      return customRequest(config, this.axiosInstance.request)
    }
    if (cacheConfig.enable) {
      const value = this.cache.get(config)
      if (value) return value

      const promise = this.axiosInstance.request(config)
      this.cache.set(config, promise, cacheConfig)

      promise.catch((reason) => {
        if (!cacheConfig.failedReq || axios.isCancel(reason)) {
          this.cache.delete(config)
        }
      })
      return promise
    }

    return this.axiosInstance.request(config)
  }

  async request (config, options) {
    let conf = cloneDeep(config)
    const transform = this.getTransform()
    const requestOptions = get(this.options, 'requestOptions', {})
    const opt = Object.assign({}, requestOptions, options)

    const {
      successRequestHook,
      afterRequestHook,
      beforeRequestHook,
      requestCatchHook,
      transformRequestHook
    } = transform || {}

    if (beforeRequestHook && isFunction(beforeRequestHook)) {
      conf = beforeRequestHook(conf, opt)
    }

    conf.requestOptions = opt

    return new Promise((resolve, reject) => {
      this.useCache(conf).then(res => {
        if (successRequestHook && isFunction(successRequestHook)) {
          successRequestHook(conf, res)
        }

        if (transformRequestHook && isFunction(transformRequestHook)) {
          try {
            const ret = transformRequestHook(res, opt)
            resolve(ret)
          } catch (err) {
            reject(err || new Error('request error!'))
          }
          return
        }
        resolve(res)
      })
        .catch(error => {
          if (requestCatchHook && isFunction(requestCatchHook)) {
            reject(requestCatchHook(error, opt))
            return
          }
          // if (axios.isAxiosError(error)) { }
          reject(error)
        })
        .finally(() => {
          if (afterRequestHook && isFunction(afterRequestHook)) {
            afterRequestHook(conf, opt)
          }
        })
    })
  }
}
